<!DOCTYPE html>
<html lang="en">
<head>
    <title>THREE.JS流动线Demo</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <link rel="stylesheet" href="css/main.css">
</head>
<body>

<div id="container"></div>
<div id="title">
    <h1>THREE.JS流动线Demo</h1>
</div>
</body>

<script src="js/three.min.js"></script>
<script src="js/OrbitControls.js"></script>
<script src="js/THREE.MeshLine.js"></script>

<script>

    let scene = new THREE.Scene();

    let SCREEN_WIDTH = window.innerWidth,
        SCREEN_HEIGHT = window.innerHeight,
        SCREEN_WIDTH_HALF = SCREEN_WIDTH / 2,
        SCREEN_HEIGHT_HALF = SCREEN_HEIGHT / 2;

    let camera, controls, renderer;

    var boid, boids;

    // 线条数量
    const LINE_LENGTH = 2;

    let resolution = new THREE.Vector2(window.innerWidth, window.innerHeight);

    // 创建线条到方法
    let Boid = function () {

        let self = this;

        let vector = new THREE.Vector3(),
            _acceleration, _width = 500, _height = 500, _depth = 200, _goal, _neighborhoodRadius = 100,
            _maxSpeed = 4, _maxSteerForce = 0.1, _avoidWalls = false;

        this.position = new THREE.Vector3();
        this.velocity = new THREE.Vector3();
        _acceleration = new THREE.Vector3();
        this.trail_initialized = false;

        setTimeout(function () {
            self.initTrail();
        }, 250);

        this.initTrail = function () {
            // 创建存储顶点的线几何体
            this.trail_geometry = new THREE.Geometry();
            for (let i = 0; i < 50; i++) {
                // 线条尾长设置
                this.trail_geometry.vertices.push(this.position.clone());
            }

            // 线条
            this.trail_line = new MeshLine();
            this.trail_line.setGeometry(this.trail_geometry, function (p) {
                return p;
            }); // makes width taper

            //线条材质
            this.trail_material = new MeshLineMaterial({
                // color: new THREE.Color("rgb(0, 0, 255)"),
                color: new THREE.Color("rgb(225, 125, 312)"),
                opacity: 0.8,
                resolution: resolution,
                sizeAttenuation: 1,
                lineWidth: 6,
                depthTest: true,
                blending: THREE.AdditiveBlending,
                transparent: true,
                side: THREE.DoubleSide
            });

            this.trail_mesh = new THREE.Mesh(this.trail_line.geometry, this.trail_material);
            this.trail_mesh.frustumCulled = false;

            scene.add(this.trail_mesh);

            this.trail_initialized = true;
        };

        this.setAvoidWalls = function (value) {

            _avoidWalls = value;

        };

        this.setWorldSize = function (width, height, depth) {

            _width = width;
            _height = height;
            _depth = depth;

        };

        this.run = function (boids) {
            // 矩阵变换
            if (_avoidWalls) {

                vector.set(-_width, this.position.y, this.position.z);
                vector = this.avoid(vector);
                vector.multiplyScalar(5);
                _acceleration.add(vector);

                vector.set(_width, this.position.y, this.position.z);
                vector = this.avoid(vector);
                vector.multiplyScalar(5);
                _acceleration.add(vector);

                vector.set(this.position.x, -_height, this.position.z);
                vector = this.avoid(vector);
                vector.multiplyScalar(5);
                _acceleration.add(vector);

                vector.set(this.position.x, _height, this.position.z);
                vector = this.avoid(vector);
                vector.multiplyScalar(5);
                _acceleration.add(vector);

                vector.set(this.position.x, this.position.y, -_depth);
                vector = this.avoid(vector);
                vector.multiplyScalar(5);
                _acceleration.add(vector);

                vector.set(this.position.x, this.position.y, _depth);
                vector = this.avoid(vector);
                vector.multiplyScalar(5);
                _acceleration.add(vector);

            }

            if (Math.random() > 0.5) {

                this.flock(boids);

            }

            this.move();

        };

        this.flock = function (boids) {

            if (_goal) {

                _acceleration.add(this.reach(_goal, 0.005));

            }

            _acceleration.add(this.alignment(boids));
            _acceleration.add(this.cohesion(boids));
            _acceleration.add(this.separation(boids));

        };

        this.move = function () {

            this.velocity.add(_acceleration);

            let l = this.velocity.length();

            if (l > _maxSpeed) {

                this.velocity.divideScalar(l / _maxSpeed);

            }

            this.position.add(this.velocity);
            _acceleration.set(0, 0, 0);

            // 线条根据前进到下一个位置
            if (this.trail_initialized) this.trail_line.advance(this.position);
        };

        this.avoid = function (target) {

            let steer = new THREE.Vector3();

            steer.copy(this.position);
            steer.sub(target);

            steer.multiplyScalar(1 / this.position.distanceToSquared(target));

            return steer;

        };

        this.repulse = function (target) {

            let distance = this.position.distanceTo(target);

            if (distance < 150) {

                let steer = new THREE.Vector3();

                steer.subVectors(this.position, target);
                steer.multiplyScalar(0.5 / distance);

                _acceleration.add(steer);

            }

        };

        this.reach = function (target, amount) {

            let steer = new THREE.Vector3();

            steer.subVectors(target, this.position);
            steer.multiplyScalar(amount);

            return steer;

        };

        this.alignment = function (boids) {

            let boid, velSum = new THREE.Vector3(),
                count = 0;

            for (let i = 0, il = boids.length; i < il; i++) {

                if (Math.random() > 0.6) continue;

                boid = boids[i];

                let distance = boid.position.distanceTo(this.position);

                if (distance > 0 && distance <= _neighborhoodRadius) {

                    velSum.add(boid.velocity);
                    count++;

                }

            }

            if (count > 0) {

                velSum.divideScalar(count);

                let l = velSum.length();

                if (l > _maxSteerForce) {

                    velSum.divideScalar(l / _maxSteerForce);

                }

            }

            return velSum;

        };

        this.cohesion = function (boids) {

            let boid, distance,
                posSum = new THREE.Vector3(),
                steer = new THREE.Vector3(),
                count = 0;

            for (let i = 0, il = boids.length; i < il; i++) {

                if (Math.random() > 0.6) continue;

                boid = boids[i];
                distance = boid.position.distanceTo(this.position);

                if (distance > 0 && distance <= _neighborhoodRadius) {

                    posSum.add(boid.position);
                    count++;

                }

            }

            if (count > 0) {

                posSum.divideScalar(count);

            }

            steer.subVectors(posSum, this.position);

            let l = steer.length();

            if (l > _maxSteerForce) {

                steer.divideScalar(l / _maxSteerForce);

            }

            return steer;

        };

        this.separation = function (boids) {

            let boid, distance,
                posSum = new THREE.Vector3(),
                repulse = new THREE.Vector3();

            for (let i = 0, il = boids.length; i < il; i++) {

                if (Math.random() > 0.6) continue;

                boid = boids[i];
                distance = boid.position.distanceTo(this.position);

                if (distance > 0 && distance <= _neighborhoodRadius) {

                    repulse.subVectors(this.position, boid.position);
                    repulse.normalize();
                    repulse.divideScalar(distance);
                    posSum.add(repulse);

                }

            }

            return posSum;

        }

    }


    init();
    animate();

    // 初始化
    function init() {

        camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, .1, 1000);
        camera.position.set(0, 0, -500);

        let container = document.getElementById('container');

        renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setPixelRatio(window.devicePixelRatio);
        container.appendChild(renderer.domElement);

        boids = [];

        for (let i = 0; i < LINE_LENGTH; i++) {

            boid = boids[i] = new Boid();
            boid.position.x = Math.random() * 400 - 200;
            boid.position.y = Math.random() * 400 - 200;
            boid.position.z = Math.random() * 400 - 200;
            boid.velocity.x = Math.random() * 2 - 1;
            boid.velocity.y = Math.random() * 2 - 1;
            boid.velocity.z = Math.random() * 2 - 1;
            boid.setAvoidWalls(true);
            boid.setWorldSize(300, 300, 300);
        }

        controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        controls.dampingFactor = 0.25;
        // 是否开启视野缩放
        controls.enableZoom = true;

        // 监听鼠标事件
        document.addEventListener('mousemove', onDocumentMouseMove, false);
        // 监听窗口变化事件
        window.addEventListener('resize', onWindowResize, false);
        onWindowResize();

    }

    function onWindowResize() {

        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();

        resolution.set(window.innerWidth, window.innerHeight);
        this.boids.forEach(function (boid) {
            if (boid.trail_initialized) boid.trail_material.uniforms.resolution.value.copy(resolution);
        });

        renderer.setSize(window.innerWidth, window.innerHeight);

    }

    function onDocumentMouseMove(event) {

        var vector = new THREE.Vector3(event.clientX - SCREEN_WIDTH_HALF, -event.clientY + SCREEN_HEIGHT_HALF, 0);

        for (var i = 0, il = boids.length; i < il; i++) {

            boid = boids[i];

            vector.z = boid.position.z;

            boid.repulse(vector);

        }

    }

    function animate() {
        requestAnimationFrame(animate);
        controls.update();
        // 让线条随机移动
        for (let i = 0, il = LINE_LENGTH; i < il; i++) {
            boid = boids[i];
            boid.run(boids);
        }
        render();
    }

    function render() {
        renderer.render(scene, camera);
    }

</script>

</html>
